﻿using System;
using System.Collections.Generic;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Windows;
using System.Diagnostics;
using System.Linq;
using XAMLStylePad.ViewModels;
using System.Collections;
using System.IO;
using FireFly;

namespace XAMLStylePad
{
    public class XamlSPViewModelService : ViewModelService
    {
        static Dictionary<Type, Type> _ConcreteTypes = new Dictionary<Type, Type>();
        static Dictionary<Type, ImageSource> _controlsIcons = new Dictionary<Type, ImageSource>();

        #region overrides
        public override ImageSource GetIcon(string key)
        {
            if(Application.Current == null || Application.Current.Resources == null)
                return null;
            if(Application.Current.Resources.MergedDictionaries.Count == 0)
            {// there was bug with lossing resources after loading some assemblies
                Application.Current.Resources.MergedDictionaries.Add(new ResourceDictionary()
                {
                    Source = new Uri("Themes/StringAndIconsResources.xaml", UriKind.Relative)
                });
            }
            if(Application.Current.Resources.Contains(key + ".Icon"))
            {
                var icon = (ImageSource)Application.Current.TryFindResource(key + ".Icon");
                return icon ?? new BitmapImage();
            } else
                return null;
        }

        /// <summary>
        /// Extract string resource from application resources by key
        /// </summary>
        public override string GetString(string key)
        {
            return (string)Application.Current.TryFindResource(key) ?? "!!!" + key + "!!!";
        }

        public override CT GetAppContext<CT>()
        {
            if(typeof(CT) == typeof(IUIContext) ||
                typeof(IUIContext).IsSubclassOf(typeof(CT)))
                return (CT)XAMLStylePadMainWindow.Instance;
            else
                throw new ArgumentException("Wrong UIContext type! Must be IUIContext");
            //return default(CT);
        }

        public override IList CreateMenuItems(IViewModel viewmodel, IEnumerable<IRelayCommand> commands)
        {
            var list = base.CreateMenuItems(viewmodel, commands);

            if(list.Count > 0)
                list.Add(new Separator());

            list.Add(CreateExpandMenu<IViewModel>(null, null));

            return list;
        }
        #endregion

        private static MenuItem CreateExpandMenu<T>(ContextMenu menu, IViewModel viewModel) where T : IViewModel
        {
            //expansion submenu
            var expandMenu = new MenuItem()
            {
                Header = "Expansion"
            };

            expandMenu.Items.Add(new RelayCommand<T>(
                context => SetExpansion<T>(context, true, ExpansionTarget.SingleItem),
                context => context != null,
                "ExpandMenu.Expand").CreateMenuItem(menu, viewModel));
            expandMenu.Items.Add(new RelayCommand<T>(
                context => SetExpansion<T>(context, false, ExpansionTarget.SingleItem),
                context => context != null,
                "ExpandMenu.Collapse").CreateMenuItem(menu, viewModel));
            expandMenu.Items.Add(new RelayCommand<T>(
                context => SetExpansion<T>(context, true, ExpansionTarget.SubTree),
                context => context != null,
                "ExpandMenu.ExpandSubtree").CreateMenuItem(menu, viewModel));
            expandMenu.Items.Add(new RelayCommand<T>(
                context => SetExpansion<T>(context, false, ExpansionTarget.SubTree),
                context => context != null,
                "ExpandMenu.CollapseSubtree").CreateMenuItem(menu, viewModel));
            expandMenu.Items.Add(new RelayCommand<T>(
                context => SetExpansion<T>(context, true, ExpansionTarget.All),
                context => context != null,
                "ExpandMenu.ExpandAll").CreateMenuItem(menu, viewModel));
            expandMenu.Items.Add(new RelayCommand<T>(
                context => SetExpansion<T>(context, false, ExpansionTarget.All),
                context => context != null,
                "ExpandMenu.CollapseAll").CreateMenuItem(menu, viewModel));
            return expandMenu;
        }

        /// <summary>
        /// Tree item expansion commands common logic
        /// </summary>
        /// <param name="isExpanded">is items need to be expanded</param>
        /// <param name="expansionTarget">level of expansion</param>
        private static void SetExpansion<T>(CommandExecutionContext<T, IUIContext> context, bool isExpanded, ExpansionTarget expansionTarget) where T : IViewModel
        {
            var ctrl = context.PlacementTarget;
            while(ctrl != null && !(ctrl is TreeViewItem))
                ctrl = VisualTreeHelper.GetParent(ctrl) as UIElement;

            if(expansionTarget == ExpansionTarget.SingleItem)
            {
                ((TreeViewItem)ctrl).IsExpanded = isExpanded;
                return;
            }

            if(expansionTarget == ExpansionTarget.All)
            {
                while(ctrl != null && !(ctrl is TreeView))
                    ctrl = VisualTreeHelper.GetParent(ctrl) as UIElement;
            }

            var items = GetNestedVisualChildren<TreeViewItem>(ctrl);

            foreach(TreeViewItem item in items)
            {
                item.IsExpanded = isExpanded;
                //if(isExpanded)
                //    Expand(item as TreeViewItem);
                //else
                //    Collapse(item as TreeViewItem);
            }
        }

        enum ExpansionTarget
        {
            SingleItem,
            SubTree,
            All
        }

        protected override ContextMenu GetContextMenu(MenuItem item)
        {
            DependencyObject parent = item;

            while(parent != null && !(parent is ContextMenu))
                parent = LogicalTreeHelper.GetParent(parent);

            return (ContextMenu)parent;
        }

        /// <summary>
        /// Extract list of dictionaries of given FrameworkElement
        /// </summary>
        public static List<ResourceDictionary> GetDictionaries(FrameworkElement obj)
        {
            var list = (from element in GetNestedVisualChildren<FrameworkElement>(obj)
                        where element.Resources != null
                        select element.Resources).ToList();

            return list;
        }

        public static ImageSource GetToolboxIcon(object component)
        {
            Type type = component.GetType();
            if(!_controlsIcons.ContainsKey(type))
            {
                var attrs = type.GetCustomAttributes(typeof(System.Drawing.ToolboxBitmapAttribute), true);
                if(attrs != null && attrs.Length > 0)
                {
                    BitmapSource res = null;
                    var stream = new MemoryStream();

                    try
                    {
                        var ico = ((System.Drawing.ToolboxBitmapAttribute)attrs[0]).GetImage(component);
                        ico.Save(stream, System.Drawing.Imaging.ImageFormat.Icon);
                        res = new BitmapImage()
                        {
                            StreamSource = stream
                        };
                    } catch(Exception err)
                    {
                        Debug.WriteLine(err.Message + "\n" + err.StackTrace);
                    } finally
                    {
                        if(stream != null)
                            stream.Dispose();
                    }

                    _controlsIcons.Add(type, res);
                } else
                    _controlsIcons.Add(type, ViewModelService.Instance.GetIcon(typeof(ObjectViewModel)));
            }

            return _controlsIcons[type];
        }

        /// <summary>
        /// Create sample object for DataTemplates
        /// </summary>
        /// <param name="dataType">type of sample object</param>
        internal static object CreateDataObject(Type dataType)
        {
            return CreateInstance(dataType);
        }

        /// <returns>
        /// null if creation of object is failed
        /// </returns>
        public static object CreateInstance(Type type)
        {
            if(!_ConcreteTypes.ContainsKey(type))
            {
                if(type.IsAbstract || !HasParameterlessConstructor(type))
                { // if type is abstract or doesn't have parameterless constructor, then search between it's derived types
                    var derivedTypes = (from t in type.Assembly.GetTypes()
                                        where t.BaseType == type && !t.IsAbstract
                                        select t).ToList();

                    if(derivedTypes == null || derivedTypes.Count == 0)
                    {
                        derivedTypes = (from a in AppDomain.CurrentDomain.GetAssemblies()
                                        where a != type.Assembly  //type.Assembly already checked
                                        select a into asm
                                        from t in asm.GetTypes()
                                        where t.BaseType == type && !t.IsAbstract
                                        select t).ToList();
                    }

                    if(derivedTypes != null && derivedTypes.Count > 0)
                        foreach(var t in derivedTypes)
                        {
                            if(HasParameterlessConstructor(t))
                            {
                                _ConcreteTypes.Add(type, t);
                                break;
                            }
                        } else
                        _ConcreteTypes.Add(type, null);
                } else
                    _ConcreteTypes.Add(type, type);
            }

            if(_ConcreteTypes.ContainsKey(type) && _ConcreteTypes[type] != null)
                return Activator.CreateInstance(_ConcreteTypes[type]);
            else
                return null;
        }

        /// <summary>
        /// Check type for parameterless constructor
        /// </summary>
        public static bool HasParameterlessConstructor(Type type)
        {
            return (from c in type.GetConstructors()
                    where c.GetParameters().Count() == 0
                    select c).Count() > 0;
        }


        /// <summary>
        /// Show error message on preview panel
        /// </summary>
        public override void ProcessException(Exception err)
        {
            XAMLStylePadMainWindow.Instance.PreviewPanel.PreviewErrorMessage(err);
        }

        
    }
}